/*
-----------------------------------------------------------------------------
This file is part of libESM.

Copyright (c) 2008 Jacob Essex

libESM is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

libESM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libESM.  If not, see <http://www.gnu.org/licenses/>.
-----------------------------------------------------------------------------
*/

#include "esm.h"

namespace ESM {
#ifdef ESM_IOSTREAM
    //----------------------------------------------
    std::ostream &operator<<(std::ostream &stream, Exception& e) {
        stream << "An Error Occured:\n" << e.getDescription() << "\n\nThe Error occured in:\n"
        << e.getFile() << "\nIn the function" << e.getFunction() << "\nOn Line" << e.getLine();
        return stream;
    }
    //----------------------------------------------
    std::ostream &operator<<(std::ostream &stream, SubRecord& t) {
        stream << fromLong(t.getType()) << " : " << t.getData();
        return stream;
    }
    //----------------------------------------------
    std::ostream &operator<<(std::ostream &stream, SubRecordPtr t) {
        stream << fromLong(t->getType()) << " : " << t->getData();

        return stream;
    }
    //----------------------------------------------
    std::ostream &operator<<(std::ostream &stream, Record& t) {
        stream << fromLong(t.getType()) << "\n";
        for ( SubRecordListItr itr = t.begin(); itr != t.end(); ++itr )
            stream << "\t" << *itr << "\n";
        return stream;
    }
    //----------------------------------------------
    std::ostream &operator<<(std::ostream &stream, RecordPtr t) {
        stream << fromLong(t->getType()) << "\n";
        for ( SubRecordListItr itr = t->begin(); itr != t->end(); ++itr )
            stream << "\t" << *itr << "\n";
        return stream;
    }
#endif
    //----------------------------------------------

    //--------------------------------------------------------------------------------------------

    //----------------------------------------------

    SubRecord::SubRecord(long type, char* data, long dataLength)
            : mType(type), mSubData(data,dataLength), mSubDataLength(dataLength) {}
    //----------------------------------------------
    SubRecord::SubRecord(long type, const std::string& data)
            : mType(type), mSubData(data), mSubDataLength((long)data.length()) {}

    //----------------------------------------------
    SubRecord::~SubRecord() {
    }
    //----------------------------------------------
    bool SubRecord::equals(const SubRecordPtr rhs) const{
        if ( getType() != rhs->getType() ||
                getDataSize() != rhs->getDataSize() ||
                getData() != rhs->getData() )
            return false;
        return true;
    }
    //----------------------------------------------

    //--------------------------------------------------------------------------------------------

    //----------------------------------------------
    Record::Record( long type ) : mType(type) {}
    //----------------------------------------------
    Record::~Record() {
#ifndef ESM_BOOST_PTR
        const SubRecordListCItr end = mSubRecordList.end();
        for ( SubRecordListCItr i = mSubRecordList.begin(); i != end; ++i )
            delete *i;
#endif
    }
    //----------------------------------------------
    bool Record::equals(const RecordPtr rhs) const {
        if ( getType() != rhs->getType() ) //check type first for speed
            return false;
        if ( subRecordCount() != rhs->subRecordCount() ) //check these are the same
            return false;


        const SubRecordListCItr end = mSubRecordList.end(); //speed, as the list doesn't change
        for ( SubRecordListCItr i = mSubRecordList.begin(); i != end; ++i ) { //check that it has the exact
            if ( rhs->hasSubRecord(*i) == false )                           //same sub record
                return false;
        }
        return true;
    }
    //----------------------------------------------
    bool Record::hasSubRecord(SubRecordPtr s) const {
        const SubRecordListCItr end = mSubRecordList.end(); //outside loop for speed
        for ( SubRecordListCItr i = mSubRecordList.begin(); i != end; ++i ) {
            if ( (*i)->equals(s) )
                return true;
        }
        return false;
    }
    //----------------------------------------------
    void Record::deleteSubRecord(long t) {
#ifdef ESM_STORE_IN_SUB_MAP
        SubRecordMapItr i = mSubRecordMap.find(t);
        if ( i == mSubRecordMap.end() )
            return;
        delete i->second;
        mSubRecordList.remove(i->second);
        //mSubRecordMap.erase(i);
        mSubRecordMap.erase(i->first);
#else
        const SubRecordListCItr end = mSubRecordList.end();
        for ( SubRecordListCItr i = mSubRecordList.begin(); i != end; ++i ) {
            if ( (*i)->getType() == t ) {
#ifndef ESM_BOOST_PTR
                delete (*i);
#endif
                mSubRecordList.remove(*i);
                break;
            }
        }
#endif
    }
    //----------------------------------------------
    void Record::deleteSubRecord(SubRecordPtr s) {

        ESM_CHECK(s, "Cannot delete a null ptr");

#ifdef ESM_WARNINGS
        size_t _ls = mSubRecordList.size();
#endif
        mSubRecordList.remove(s);
        ESM_CHECK_WARNING(_ls!=mSubRecordList.size(), "A non existant sub record was passed");

#ifdef ESM_STORE_IN_SUB_MAP

        for ( SubRecordMapCItr i = mSubRecordMap.begin();
                i != mSubRecordMap.end();
                ++i) {
            if ( i->second == s ) {
                //mSubRecordMap.erase(i);
                mSubRecordMap.erase(i->first);
                break;
            }
        }

#endif
#ifndef ESM_BOOST_PTR
        delete s;
#endif
    }
    //----------------------------------------------
    SubRecordPtr Record::getUniqueSubRecord(long t) {
#ifdef ESM_STORE_IN_SUB_MAP
        //returns last added item with that name
        SubRecordMapCItr i = mSubRecordMap.find(t);
        if ( i == mSubRecordMap.end() )
            return 0;

        return i->second;
#else
        //returns first added item with that name
        const SubRecordListCItr end = mSubRecordList.end();
        for ( SubRecordListCItr i = mSubRecordList.begin(); i != end; ++i ) {
            if ( (*i)->getType() == t )
                return *i;
        }
#endif
#ifdef ESM_BOOST_PTR
        return SubRecordPtr();
#else
        return 0;
#endif
    }
    //----------------------------------------------


    bool Record::hasSubRecord( long recordName ) const {
#ifdef ESM_STORE_IN_SUB_MAP
        if ( mSubRecordMap.find(recordName ) != mSubRecordMap.end() )
            return true;
#else
        const SubRecordListCItr end = mSubRecordList.end();
        for ( SubRecordListCItr i = mSubRecordList.begin(); i != end; ++i ) {
            if ( (*i)->getType() == recordName )
                return true;
        }
#endif
        return false;
    }
    //----------------------------------------------

    //--------------------------------------------------------------------------------------------

    //----------------------------------------------
#ifdef ESM_PY
    PyRecordList File::getRecordsByType( long t ) {
#else
    RecordList File::getRecordsByType( long t ) {
#endif
#ifdef ESM_PY
        return mSortedRecords[t];
#else
        RecordList r;
        for ( RecordListItr iter = mRecordList.begin(); iter != mRecordList.end(); ++iter) {
            if ( t == (*iter)->getType() )
                r.push_back(*iter);
        }
        return r;
#endif

    }
    //----------------------------------------------
#ifdef ESM_BOOST_PTR
    File::File(bool loadTypes /*= false*/) : mLoadAllTypes(loadTypes) {
    }
#else
    File::File(bool loadTypes /*= false*/) : mHeader(0), mLoadAllTypes(loadTypes) {
    }
#endif
//----------------------------------------------
    File::~File() {
#ifndef ESM_BOOST_PTR
        for ( RecordListItr itr = mRecordList.begin(); itr != mRecordList.end(); ++itr )
            delete *itr;

        if ( mHeader )
            delete mHeader;
#endif //ESM_BOOST_PTR
    }
    //----------------------------------------------
    void File::deleteRecord(RecordPtr r) {
        ESM_CHECK(r, "Cannot delete a null ptr");

#ifdef ESM_WARNINGS
        size_t _ls = mRecordList.size();
#endif
        mRecordList.remove(r);
        ESM_CHECK_WARNING(_ls!=mRecordList.size(), "A non existant sub record was passed");

#ifdef ESM_STORE_IN_RECORD_MAP
        const RecordMapCItr end = mRecords.end();
        for ( RecordMapItr iter = mRecords.begin(); iter != end; ++iter) {
            if ( r == iter->second ) {
                mRecords.erase(iter);
                break;
            }
        }
#endif
#ifndef ESM_BOOST_PTR
        delete r;
#endif //ESM_BOOST_PTR
    }
    //----------------------------------------------
    void File::load( const char* file, unsigned int memToAlloc /* = 32768 */ ) {
        std::ifstream esp (file, std::ios::in | std::ios::binary);
        ESM_CHECK(esp.is_open(), "Failed to open file");

        mLoadedFile = file;

        unsigned int storageLen = 32;
        if ( storageLen < 16 ) storageLen = 32; //always alloc 32 bytes of data storage
        char* tempStorage = new char[storageLen];

        esp.seekg(0, std::ios::end);
        long fileSize = esp.tellg();
        esp.seekg(0);


        addLoadable("TES3"); //header must always be loadable
        mHeader = readRecord(esp, tempStorage, storageLen);

        long numRecords = getOriganalNumberOfRecords();

        for ( long i = 0; i < numRecords; i++ ) {
            RecordPtr record = readRecord(esp, tempStorage, storageLen);
            if ( record ) _addRecord(record);

            if ( esp.tellg() >= fileSize )
                break;
        }

        delete [] tempStorage;

        esp.close();


    }
    //----------------------------------------------

    RecordPtr File::readRecord(std::ifstream& esp, char*& storage, unsigned int& storageLen) const{
        //read the 16 bytes of record header into the storage
        esp.read(storage, 16);

        ESM_CHECK(*(long*)(storage+4)>0, "Undexpected data value");
        ESM_CHECK((long)esp.tellg()>0, "Undexpected get poiner");

        long endPos = *(long*)(storage+4) + (long)esp.tellg();

        if ( isLoadable(*(long*)storage) ) {

            RecordPtr record = newRecord(*(long*)storage);
            record->setHeader( *(long*)(storage+8)  );
            record->setFlags ( *(long*)(storage+12) );

            //all data in storage invalidated here, as readSubRecords uses it
            readSubRecords(esp, record, endPos, storage, storageLen);

            //not really needed any more...
            ESM_CHECK_DEBUG(endPos==esp.tellg(), "Read pointer in unexpected place");
            return record;
        } else {
            esp.seekg(endPos);
            ESM_CHECK_DEBUG(endPos==esp.tellg(), "Read pointer in unexpected place");
        }
#ifdef ESM_BOOST_PTR
        return RecordPtr();
#else
        return 0;
#endif
    }
    //----------------------------------------------
    void File::readSubRecords(std::ifstream& esp, RecordPtr record, long end, char*& storage, unsigned int& storageLen) const{
        unsigned long length = end - (long)esp.tellg();
        unsigned long p = 0;

        //check correct length
        if ( length > storageLen ){
            delete [] storage;
            storageLen = length;
            storage = new char[storageLen];
        }

        esp.read (storage, length);
        while ( p < length ){
            record->createSubRecord(*(long*)(storage+p),storage+p+8, *(long*)(storage+p+4)); //copy sub data into storage
            p += 8 + *(long*)(storage+p+4); //increase p by length + 8 (the sub record header is 8 bytes)
        }
    }
    //----------------------------------------------
    void  File::save(const char* file) const {
        ESM_CHECK_WARNING(mLoadAllTypes, "Not all types were loaded, so the save may be missing data");
        std::ofstream ofs(file, std::ios::binary);

        ESM_CHECK(mHeader, "The header must exist");
        mHeader->getUniqueSubRecord("HEDR")->setLong((long)mRecordList.size(), 296);

        ESM_CHECK_DEBUG(getOriganalNumberOfRecords()==(long)mRecordList.size(), "Record size wan't set correctly");
        writeRecord(ofs,mHeader);

        for ( RecordListCItr itr = mRecordList.begin(); itr != mRecordList.end(); ++itr ) //{
            writeRecord(ofs,*itr);
    }
    //----------------------------------------------
    void  File::writeRecord(std::ofstream& ofs, RecordPtr r) const{
        ESM_CHECK_DEBUG(r, "Must pass a valid record for writing");

        //maybe I should attempt to make this code nicer....

        long temp = r->getType();
        ofs.write((char*)&temp, 4);

        //store position where record size is written
        long sizePos = ofs.tellp();
        ofs.write((char*)&temp, 4);//temp value for record size

        //write header/flag data
        long t = r->getHeader();
        ofs.write((char*)&t, 4);
        t = r->getFlags();
        ofs.write((char*)&t, 4);
        long size = 0;

        for ( SubRecordListItr itr = r->begin(); itr != r->end(); ++itr )
            size += writeSubRecord(ofs, *itr);

        //store cur pos
        t = ofs.tellp();
        ofs.seekp(sizePos); //return to the place where the size should be written
        ofs.write((char*)&size, 4); //write
        ofs.seekp(t); //go back to end of file
    }
    //----------------------------------------------
    long  File::writeSubRecord(std::ofstream& ofs, SubRecordPtr r) const{
        ESM_CHECK_DEBUG(r, "Must pass a valid subrecord for writing");
        long t =r->getType();
        ofs.write((char*)&t, 4);
        t =r->getDataSize();
        ofs.write((char*)&t, 4);
        ofs.write(r->getData().c_str(), r->getDataSize());
        //std::cout << "Writing " << r->getDataSize() << " bytes\n";
        return r->getDataSize() + 8;
    }
    //----------------------------------------------
}//namespace
